/*******************************************************************************
 * Copyright (c) 2012 Till Kolditz.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 * 
 * created on 17.10.2012 at 15:13:16
 * 
 *  Contributors:
 *      Till Kolditz
 *******************************************************************************/
package de.cc.yFilesParser.wizard;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

import de.cc.yFilesParser.Activator;
import de.kolditz.common.ui.ButtonBar;
import de.kolditz.common.ui.fields.BooleanField;
import de.kolditz.common.ui.fields.FieldComposite;
import de.kolditz.common.ui.fields.LabelField;
import de.kolditz.common.ui.fields.ListField;
import de.kolditz.common.ui.fields.TextField;
import de.kolditz.common.util.IObservable;
import de.kolditz.common.util.IObserver;
import de.kolditz.common.util.IObserverSkeleton;

/**
 * @author Till Kolditz - Till.Kolditz@GoogleMail.com
 */
public class ConvertSettingsPage extends WizardPage
{
    private static final Pattern SML_VARIABLE_PATTERN = Pattern.compile("[a-zA-Z][\\w_]*"); //$NON-NLS-1$

    public static final String PREF_REVERSE = "ConvertStartPage.Reverse"; //$NON-NLS-1$
    public static final String PREF_VARIABLE = "ConvertStartPage.Variable"; //$NON-NLS-1$
    public static final String PREF_NAME_PARENT = "ConvertStartPage.Name.Parent"; //$NON-NLS-1$
    public static final String PREF_NAME_CHILD = "ConvertStartPage.Name.Child"; //$NON-NLS-1$
    public static final String PREF_NAME_ATTRIBUTE = "ConvertStartPage.Name.Attribute"; //$NON-NLS-1$
    public static final String PREF_PRETTYPRINT = "ConvertStartPage.PrettyPrint"; //$NON-NLS-1$

    private static final String DESC_REVERSE = "Source -- Is-A --> Target == \"Target is a Source\""; //$NON-NLS-1$
    private static final String DESC_CORRECT = "Source -- Is-A --> Target == \"Source is a Target\""; //$NON-NLS-1$

    private ConvertWizard wizard;
    private FieldComposite fieldComp;
    private TextField tfSourceFile, tfTargetFile, tfVariableName, tfParentName, tfChildName, tfAttributeName;
    private LabelField lbReverseDesc;
    private BooleanField bfReverse, bfPrettyPrint, bfSetStandard;
    private ListField<String> lfClasses, lfAttributes;
    private ButtonBar buttonBar;

    public ConvertSettingsPage(ConvertWizard wizard)
    {
        super("ConvertSettingsPage");
        this.wizard = wizard;
        setTitle("Settings for converting a yFile from yEd");
        setDescription("Convert a yEd yFile into a form usable in a control net (SML/NJ syntax)\nProvide the necessary settings.");
        setPageComplete(false);
    }

    @Override
    public void createControl(Composite parent)
    {
        parent.setLayoutDeferred(true);

        Composite area = new Composite(parent, SWT.NONE);
        area.setLayout(new GridLayout());
        area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        fieldComp = new FieldComposite(area, SWT.NONE);
        fieldComp.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));

        tfSourceFile = new TextField(fieldComp, SWT.SINGLE | SWT.READ_ONLY, "Source file:");
        tfTargetFile = new TextField(fieldComp, SWT.SINGLE | SWT.READ_ONLY, "Target file:");

        lfClasses = new ListField<String>(fieldComp, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL, "Class node shapes:", null,
                null);
        lfAttributes = new ListField<String>(fieldComp, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL,
                "Attribute node shapes:", null, null);

        bfReverse = new BooleanField(fieldComp, SWT.NONE, "Reverse inheritence:", true, DESC_REVERSE, false);
        bfReverse.setValue(Boolean.TRUE);
        bfReverse.registerObserver(new IObserverSkeleton<Boolean>()
        {
            @Override
            public void update(IObservable<Boolean> source, Boolean data)
            {
                bfReverse.setDescription(Boolean.TRUE.equals(data) ? DESC_REVERSE : DESC_CORRECT);
            }
        });
        lbReverseDesc = new LabelField(fieldComp, SWT.WRAP, "In yEd, you may have to create trees by "
                + "using an \"inverted\" edge direction. This means, that the edge goes from the parent node to the"
                + " child node. In an \"Is-A\"-relation this is actually the reverse direction of the intended"
                + " meaning. So leave this field ticked when this is the case for you.", true);
        lbReverseDesc.setWidthHint(Math.min(getShell().getDisplay().getBounds().width / 2, 600));

        tfVariableName = new TextField(fieldComp, SWT.BORDER | SWT.SINGLE, "SML/NJ variable name:",
                "e.g. initial_knowledge");

        tfParentName = new TextField(fieldComp, SWT.BORDER | SWT.SINGLE, "Node parent name:", "e.g. p");
        tfChildName = new TextField(fieldComp, SWT.BORDER | SWT.SINGLE, "Node child name:", "e.g. c");
        tfAttributeName = new TextField(fieldComp, SWT.BORDER | SWT.SINGLE, "Node attribute name:", "e.g. a");
        bfPrettyPrint = new BooleanField(fieldComp, SWT.NONE, "Pretty print:", true);

        new LabelField(fieldComp, SWT.SEPARATOR | SWT.HORIZONTAL, "", false).setLayoutData(new GridData(SWT.FILL,
                SWT.BOTTOM, true, true));
        bfSetStandard = new BooleanField(fieldComp, SWT.NONE, "Set as standard", false);

        buttonBar = new ButtonBar(area, SWT.NONE);
        buttonBar.createButton(0, "Re-check Source").addSelectionListener(new SelectionAdapter()
        {
            @Override
            public void widgetSelected(SelectionEvent e)
            {
                checkSourceFile();
                wizard.getContainer().getShell().layout(true, true);
            }
        });

        applyStandard();
        parent.setLayoutDeferred(false);
        setControl(area);
        setListeners();
        validate();
    }

    @Override
    public void setVisible(boolean visible)
    {
        super.setVisible(visible);
        if (visible)
        {
            if (wizard.startPage.isSourceChanged())
            {
                wizard.startPage.resetSourceChanged();
                tfSourceFile.setValue(wizard.startPage.getSourceFile().getAbsolutePath());
                checkSourceFile();
            }
            if (wizard.startPage.isTargetChanged())
            {
                wizard.startPage.resetTargetChanged();
                tfTargetFile.setValue(wizard.startPage.getTargetFile().getAbsolutePath());
            }
            wizard.getContainer().getShell().pack();
            // wizard.getContainer().getShell().layout(true, true);
        }
    }

    private void checkSourceFile()
    {
        try
        {
            CheckGraphmlSourceRunnable checker = new CheckGraphmlSourceRunnable(wizard);
            wizard.getContainer().run(true, true, checker);
            Set<String> nodeStyles = checker.getNodeStyles();
            lfClasses.setItems(nodeStyles);
            boolean multiNodeShapes = nodeStyles.size() > 1;
            lfAttributes.setEnabled(multiNodeShapes);
            tfAttributeName.setEnabled(multiNodeShapes);
            if (multiNodeShapes)
            {
                lfAttributes.setItems(nodeStyles);
            }
            else
            {
                lfAttributes.setItems(new String[] { "" });
            }
            if (nodeStyles.size() == 1)
            {
                lfClasses.setValue(nodeStyles.iterator().next());
            }
        }
        catch (InvocationTargetException e)
        {
            e.printStackTrace();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    private void applyStandard()
    {
        IPreferenceStore store = Activator.getDefault().getPreferenceStore();
        if (!store.isDefault(PREF_REVERSE))
            bfReverse.setValue(store.getBoolean(PREF_REVERSE));
        if (!store.isDefault(PREF_VARIABLE))
            tfVariableName.setValue(store.getString(PREF_VARIABLE));
        if (!store.isDefault(PREF_NAME_PARENT))
            tfParentName.setValue(store.getString(PREF_NAME_PARENT));
        if (!store.isDefault(PREF_NAME_CHILD))
            tfChildName.setValue(store.getString(PREF_NAME_CHILD));
        if (!store.isDefault(PREF_NAME_ATTRIBUTE))
            tfAttributeName.setValue(store.getString(PREF_NAME_ATTRIBUTE));
        if (!store.isDefault(PREF_PRETTYPRINT))
            bfPrettyPrint.setValue(store.getBoolean(PREF_PRETTYPRINT));
    }

    private void setListeners()
    {
        IObserver<String> observer = new IObserverSkeleton<String>()
        {
            @Override
            public void update(IObservable<String> source, String data)
            {
                validate();
            }

            @Override
            public void update(IObservable<String> source, Collection<String> data)
            {
                validate();
            }

            @Override
            public void update(IObservable<String> source, String[] data)
            {
                validate();
            }
        };
        tfVariableName.registerObserver(observer);
        tfParentName.registerObserver(observer);
        tfChildName.registerObserver(observer);
        tfAttributeName.registerObserver(observer);
        lfAttributes.registerObserver(observer);
        lfClasses.registerObserver(observer);
    }

    protected void validate()
    {
        StringBuilder sb = new StringBuilder();
        String variable = getVariableName();
        int numInvalid = 0;
        boolean varValid = variable != null && SML_VARIABLE_PATTERN.matcher(variable).matches();
        if (!varValid)
        {
            addToErrorMessage(sb, "variable name");
            ++numInvalid;
        }
        String string = getParentName();
        boolean parValid = string != null && SML_VARIABLE_PATTERN.matcher(string).matches();
        if (!parValid)
        {
            addToErrorMessage(sb, "parent name");
            ++numInvalid;
        }
        string = getChildName();
        boolean chiValid = string != null && SML_VARIABLE_PATTERN.matcher(string).matches();
        if (!chiValid)
        {
            addToErrorMessage(sb, "child name");
            ++numInvalid;
        }
        if (sb.length() > 0)
            sb.append(numInvalid > 1 ? " are" : " is").append(" not valid.");
        boolean clValid = lfClasses.getSelectionCount() > 0;
        if (!clValid)
            sb.append("no class node(s) selected");
        // attribute need not be checked since they are optional
        setPageComplete(varValid && parValid && chiValid && clValid);
        setErrorMessage(sb.length() > 0 ? sb.toString() : null);
    }

    private void addToErrorMessage(StringBuilder sb, String subMessage)
    {
        if (sb.length() > 0)
            sb.append(", ");
        sb.append(subMessage);
    }

    public String getVariableName()
    {
        return tfVariableName.getValue();
    }

    public String getParentName()
    {
        return tfParentName.getValue();
    }

    public String getChildName()
    {
        return tfChildName.getValue();
    }

    public String getAttributeName()
    {
        return tfAttributeName.getValue();
    }

    public Boolean isReverse()
    {
        return bfReverse.getValue();
    }

    public Boolean isPrettyPrint()
    {
        return bfPrettyPrint.getValue();
    }

    public Boolean isSetStandard()
    {
        return bfSetStandard.getValue();
    }

    public Collection<String> getClassShapes()
    {
        return lfClasses.getValues();
    }

    public Collection<String> getAttributeShapes()
    {
        return lfAttributes.getValues();
    }

    public void saveAsStandard()
    {
        IPreferenceStore store = Activator.getDefault().getPreferenceStore();
        Boolean b = isReverse();
        if (b != null)
            store.setValue(PREF_REVERSE, b);
        String s = getVariableName();
        if (s != null)
            store.setValue(PREF_VARIABLE, s);
        s = getParentName();
        if (s != null)
            store.setValue(PREF_NAME_PARENT, s);
        s = getChildName();
        if (s != null)
            store.setValue(PREF_NAME_CHILD, s);
        s = getAttributeName();
        if (s != null)
            store.setValue(PREF_NAME_ATTRIBUTE, s);
        b = isPrettyPrint();
        if (b != null)
            store.setValue(PREF_PRETTYPRINT, b);
    }
}
